/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Fragment operation test utilities.
 *//*--------------------------------------------------------------------*/

#include "glsFragmentOpUtil.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gls
{
namespace FragmentOpUtil
{

template<typename T>
inline T triQuadInterpolate (const T values[4], float xFactor, float yFactor)
{
	if (xFactor + yFactor < 1.0f)
		return values[0] + (values[2]-values[0])*xFactor		+ (values[1]-values[0])*yFactor;
	else
		return values[3] + (values[1]-values[3])*(1.0f-xFactor)	+ (values[2]-values[3])*(1.0f-yFactor);
}

// GLSL ES 1.0 shaders
static const char* s_glsl1VertSrc =
	"attribute highp vec4 a_position;\n"
	"attribute mediump vec4 a_color;\n"
	"varying mediump vec4 v_color;\n"
	"void main()\n"
	"{\n"
	"	gl_Position = a_position;\n"
	"	v_color = a_color;\n"
	"}\n";
static const char* s_glsl1FragSrc =
	"varying mediump vec4 v_color;\n"
	"void main()\n"
	"{\n"
	"	gl_FragColor = v_color;\n"
	"}\n";

// GLSL ES 3.0 shaders
static const char* s_glsl3VertSrc =
	"#version 300 es\n"
	"in highp vec4 a_position;\n"
	"in mediump vec4 a_color;\n"
	"out mediump vec4 v_color;\n"
	"void main()\n"
	"{\n"
	"	gl_Position = a_position;\n"
	"	v_color = a_color;\n"
	"}\n";
static const char* s_glsl3FragSrc =
	"#version 300 es\n"
	"in mediump vec4 v_color;\n"
	"layout(location = 0) out mediump vec4 o_color;\n"
	"void main()\n"
	"{\n"
	"	o_color = v_color;\n"
	"}\n";

// GLSL 3.3 shaders
static const char* s_glsl33VertSrc =
	"#version 330 core\n"
	"in vec4 a_position;\n"
	"in vec4 a_color;\n"
	"in vec4 a_color1;\n"
	"out vec4 v_color;\n"
	"out vec4 v_color1;\n"
	"void main()\n"
	"{\n"
	"	gl_Position	= a_position;\n"
	"	v_color		= a_color;\n"
	"	v_color1	= a_color1;\n"
	"}\n";
static const char* s_glsl33FragSrc =
	"#version 330 core\n"
	"in vec4 v_color;\n"
	"in vec4 v_color1;\n"
	"layout(location = 0, index = 0) out vec4 o_color;\n"
	"layout(location = 0, index = 1) out vec4 o_color1;\n"
	"void main()\n"
	"{\n"
	"	o_color  = v_color;\n"
	"	o_color1 = v_color1;\n"
	"}\n";

static const char* getVertSrc (glu::GLSLVersion glslVersion)
{
	if (glslVersion == glu::GLSL_VERSION_100_ES)
		return s_glsl1VertSrc;
	else if (glslVersion == glu::GLSL_VERSION_300_ES)
		return s_glsl3VertSrc;
	else if (glslVersion == glu::GLSL_VERSION_330)
		return s_glsl33VertSrc;

	DE_ASSERT(DE_FALSE);
	return 0;
}

static const char* getFragSrc (glu::GLSLVersion glslVersion)
{
	if (glslVersion == glu::GLSL_VERSION_100_ES)
		return s_glsl1FragSrc;
	else if (glslVersion == glu::GLSL_VERSION_300_ES)
		return s_glsl3FragSrc;
	else if (glslVersion == glu::GLSL_VERSION_330)
		return s_glsl33FragSrc;

	DE_ASSERT(DE_FALSE);
	return 0;
}

QuadRenderer::QuadRenderer (const glu::RenderContext& context, glu::GLSLVersion glslVersion)
	: m_context			(context)
	, m_program			(DE_NULL)
	, m_positionLoc		(0)
	, m_colorLoc		(-1)
	, m_color1Loc		(-1)
	, m_blendFuncExt	(!glu::glslVersionIsES(glslVersion) && (glslVersion >= glu::GLSL_VERSION_330))
{
	DE_ASSERT(glslVersion == glu::GLSL_VERSION_100_ES ||
			  glslVersion == glu::GLSL_VERSION_300_ES ||
			  glslVersion == glu::GLSL_VERSION_330);

	const glw::Functions&	gl		= context.getFunctions();
	const char*				vertSrc	= getVertSrc(glslVersion);
	const char*				fragSrc	= getFragSrc(glslVersion);

	m_program = new glu::ShaderProgram(m_context, glu::makeVtxFragSources(vertSrc, fragSrc));
	if (!m_program->isOk())
	{
		delete m_program;
		throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);
	}

	m_positionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
	m_colorLoc		= gl.getAttribLocation(m_program->getProgram(), "a_color");

	if (m_blendFuncExt)
		m_color1Loc = gl.getAttribLocation(m_program->getProgram(), "a_color1");

	if (m_positionLoc < 0 || m_colorLoc < 0 || (m_blendFuncExt && m_color1Loc < 0))
	{
		delete m_program;
		throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
	}
}

QuadRenderer::~QuadRenderer (void)
{
	delete m_program;
}

void QuadRenderer::render (const Quad& quad) const
{
	const float position[] =
	{
		quad.posA.x(), quad.posA.y(), quad.depth[0], 1.0f,
		quad.posA.x(), quad.posB.y(), quad.depth[1], 1.0f,
		quad.posB.x(), quad.posA.y(), quad.depth[2], 1.0f,
		quad.posB.x(), quad.posB.y(), quad.depth[3], 1.0f
	};
	const deUint8 indices[] = { 0, 2, 1, 1, 2, 3 };

	DE_STATIC_ASSERT(sizeof(tcu::Vec4) == sizeof(float)*4);
	DE_STATIC_ASSERT(sizeof(quad.color) == sizeof(float)*4*4);
	DE_STATIC_ASSERT(sizeof(quad.color1) == sizeof(float)*4*4);

	std::vector<glu::VertexArrayBinding> vertexArrays;

	vertexArrays.push_back(glu::va::Float(m_positionLoc,	4, 4, 0, &position[0]));
	vertexArrays.push_back(glu::va::Float(m_colorLoc,		4, 4, 0, (const float*)&quad.color[0]));

	if (m_blendFuncExt)
		vertexArrays.push_back(glu::va::Float(m_color1Loc,	4, 4, 0, (const float*)&quad.color1[0]));

	m_context.getFunctions().useProgram(m_program->getProgram());
	glu::draw(m_context, m_program->getProgram(),
			  (int)vertexArrays.size(), &vertexArrays[0],
			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

ReferenceQuadRenderer::ReferenceQuadRenderer (void)
	: m_fragmentBufferSize(0)
{
	for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_fragmentDepths); i++)
		m_fragmentDepths[i] = 0.0f;
}

void ReferenceQuadRenderer::flushFragmentBuffer (const rr::MultisamplePixelBufferAccess&	colorBuffer,
												 const rr::MultisamplePixelBufferAccess&	depthBuffer,
												 const rr::MultisamplePixelBufferAccess&	stencilBuffer,
												 rr::FaceType								faceType,
												 const rr::FragmentOperationState&			state)
{
	m_fragmentProcessor.render(colorBuffer, depthBuffer, stencilBuffer, &m_fragmentBuffer[0], m_fragmentBufferSize, faceType, state);
	m_fragmentBufferSize = 0;
}

void ReferenceQuadRenderer::render (const tcu::PixelBufferAccess&			colorBuffer,
									const tcu::PixelBufferAccess&			depthBuffer,
									const tcu::PixelBufferAccess&			stencilBuffer,
									const IntegerQuad&						quad,
									const rr::FragmentOperationState&		state)
{
	bool			flipX			= quad.posA.x() > quad.posB.x();
	bool			flipY			= quad.posA.y() > quad.posB.y();
	rr::FaceType	faceType		= flipX == flipY ? rr::FACETYPE_FRONT : rr::FACETYPE_BACK;
	int				xFirst			= flipX ? quad.posB.x() : quad.posA.x();
	int				xLast			= flipX ? quad.posA.x() : quad.posB.x();
	int				yFirst			= flipY ? quad.posB.y() : quad.posA.y();
	int				yLast			= flipY ? quad.posA.y() : quad.posB.y();
	float			width			= (float)(xLast - xFirst + 1);
	float			height			= (float)(yLast - yFirst + 1);

	for (int y = yFirst; y <= yLast; y++)
	{
		// Interpolation factor for y.
		float yRatio = (0.5f + (float)(y - yFirst)) / height;
		if (flipY)
			yRatio = 1.0f - yRatio;

		for (int x = xFirst; x <= xLast; x++)
		{
			// Interpolation factor for x.
			float xRatio = (0.5f + (float)(x - xFirst)) / width;
			if (flipX)
				xRatio = 1.0f - xRatio;

			tcu::Vec4	color	= triQuadInterpolate(quad.color, xRatio, yRatio);
			tcu::Vec4	color1	= triQuadInterpolate(quad.color1, xRatio, yRatio);
			float		depth	= triQuadInterpolate(quad.depth, xRatio, yRatio);

			// Interpolated color and depth.

			DE_STATIC_ASSERT(MAX_FRAGMENT_BUFFER_SIZE == DE_LENGTH_OF_ARRAY(m_fragmentBuffer));

			if (m_fragmentBufferSize >= MAX_FRAGMENT_BUFFER_SIZE)
				flushFragmentBuffer(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer),
									rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer),
									rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer), faceType, state);

			m_fragmentDepths[m_fragmentBufferSize] = depth;
			m_fragmentBuffer[m_fragmentBufferSize] = rr::Fragment(tcu::IVec2(x, y), rr::GenericVec4(color), rr::GenericVec4(color1), 1u /* coverage mask */, &m_fragmentDepths[m_fragmentBufferSize]);
			m_fragmentBufferSize++;
		}
	}

	flushFragmentBuffer(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer),
						rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer),
						rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer), faceType, state);
}

tcu::PixelBufferAccess getMultisampleAccess(const tcu::PixelBufferAccess& original)
{
	return tcu::PixelBufferAccess(original.getFormat(),
								  1,
								  original.getWidth(),
								  original.getHeight(),
								  original.getFormat().getPixelSize(),
								  original.getRowPitch(),
								  original.getDataPtr());
}

tcu::ConstPixelBufferAccess getMultisampleAccess(const tcu::ConstPixelBufferAccess& original)
{
	return tcu::ConstPixelBufferAccess(original.getFormat(),
									   1,
									   original.getWidth(),
									   original.getHeight(),
									   original.getFormat().getPixelSize(),
									   original.getRowPitch(),
									   original.getDataPtr());
}

} // FragmentOpUtil
} // gls
} // deqp
